package logic

import (
	"context"
	"strconv"
	"time"

	"github.com/Terry-Mao/goim/internal/logic/conf"
	"github.com/Terry-Mao/goim/internal/logic/dao"
	"github.com/Terry-Mao/goim/internal/logic/model"
	"github.com/Terry-Mao/goim/internal/registry"
	log "github.com/golang/glog"
)

const (
	_onlineTick     = time.Second * 10
	_onlineDeadline = time.Minute * 5
	_cometId        = "goim.comet"
)

// Logic struct
type Logic struct {
	c   *conf.Config
	dao *dao.Dao
	// 所有 comet 上合计的现存的 ip 数量
	totalIPs int64
	// 所有 comet 上合计的现存 channel 数量
	totalConns int64
	// 把所有 comet 上各自房间的 channel 数量统计，合并到一个 map 后的结果，会定时从 redis 中取最新的统计值
	roomCount map[string]int32
	// 活跃的 comet 实例列表
	nodes        []*registry.ServiceInstance
	loadBalancer *LoadBalancer
	regions      map[string]string // province -> region
}

// New init
func New(c *conf.Config, rs registry.Registry) (l *Logic) {
	l = &Logic{
		c:            c,
		dao:          dao.New(c),
		loadBalancer: NewLoadBalancer(),
		regions:      make(map[string]string),
	}
	l.initRegions()

	// 从注册中心拿到所有活跃的 comet
	l.initNodes(rs)

	// 定时 10s 从 redis 中拿出所有 comet 上的最新 room->连接数量 统计数据，
	// 它会作为 grpc 调用 RenewOnline 的返回值
	_ = l.loadOnline()
	go l.onlineproc()
	return l
}

// Ping ping resources is ok.
func (l *Logic) Ping(c context.Context) (err error) {
	return l.dao.Ping(c)
}

// Close close resources.
func (l *Logic) Close() {
	l.dao.Close()
}

// 建立省份到区域的映射
func (l *Logic) initRegions() {
	for region, ps := range l.c.Regions {
		for _, province := range ps {
			l.regions[province] = region
		}
	}
}

// 通过服务发现，拿到所有注册的 comet，并监听 comet 的变化
func (l *Logic) initNodes(rs registry.Registry) {
	event, err := rs.Subscribe(_cometId)
	if err != nil {
		panic(err)
	}

	select {
	case _, ok := <-event:
		if ok {
			l.newNodes(rs)
		} else {
			panic("discovery watch failed")
		}
	case <-time.After(10 * time.Second):
		log.Error("discovery start timeout")
	}
	go func() {
		for {
			if _, ok := <-event; !ok {
				return
			}
			l.newNodes(rs)
		}
	}()
}

// 每次有 comet 节点上下线，都要再次拉取最新的 comet 列表，重新统计
func (l *Logic) newNodes(rs registry.Registry) {
	if cometIns, err := rs.ListServices(context.Background(), _cometId); err == nil {
		var (
			totalConns int64
			totalIPs   int64
			allIns     []*registry.ServiceInstance
		)
		for _, ins := range cometIns {
			if ins.Metadata == nil {
				log.Errorf("node instance metadata is empty(%+v)", ins)
				continue
			}
			offline, err := strconv.ParseBool(ins.Metadata[model.MetaOffline])
			if err != nil || offline {
				log.Warningf("strconv.ParseBool(offline:%t) error(%v)", offline, err)
				continue
			}
			conns, err := strconv.ParseInt(ins.Metadata[model.MetaConnCount], 10, 32)
			if err != nil {
				log.Errorf("strconv.ParseInt(conns:%d) error(%v)", conns, err)
				continue
			}
			ips, err := strconv.ParseInt(ins.Metadata[model.MetaIPCount], 10, 32)
			if err != nil {
				log.Errorf("strconv.ParseInt(ips:%d) error(%v)", ips, err)
				continue
			}
			totalConns += conns
			totalIPs += ips
			allIns = append(allIns, ins)

		}
		l.totalConns = totalConns
		l.totalIPs = totalIPs
		l.nodes = allIns
		l.loadBalancer.Update(allIns)
	}
}

// 不断检测所有 comet 的活跃状态，并更新统计信息
func (l *Logic) onlineproc() {
	for {
		time.Sleep(_onlineTick)
		if err := l.loadOnline(); err != nil {
			log.Errorf("onlineproc error(%v)", err)
		}
	}
}

// 遍历每一个 comet，从 redis 中拿出 Online 数据，检查过期并移除，未过期的更新 comet 的 room 统计信息，
func (l *Logic) loadOnline() (err error) {
	var (
		roomCount = make(map[string]int32)
	)
	// 遍历所有 comet
	for _, server := range l.nodes {
		var online *model.Online
		online, err = l.dao.ServerOnline(context.Background(), server.Hostname)
		if err != nil {
			return
		}
		if time.Since(time.Unix(online.Updated, 0)) > _onlineDeadline { // 数据已过期
			_ = l.dao.DelServerOnline(context.Background(), server.Hostname)
			continue
		}
		for roomID, count := range online.RoomCount {
			roomCount[roomID] += count // 一个 roomID 可能在多个 comet 上都存在
		}
	}
	l.roomCount = roomCount
	return
}
